 
#ifndef __RSP_C_CONTROLLER__HEADER_FILE__
#define __RSP_C_CONTROLLER__HEADER_FILE__


#include "coid/comm/commtypes.h"
#include "coid/comm/keywordt.h"
#include "coid/comm/local.h"

#include "controller.h"
#include "c_update.h"
//#include "c_object.h"
//#include "c_objlist.h"
#include "classdef.h"

using namespace coid;

////////////////////////////////////////////////////////////////////////////////
///Client side RSP manager for reference objects
struct rsp_c_controller : rsp_controller
{
    ///Receive an update from server
    int patch( binstream& bin, uint32 ssid, uint16 slot, uchar ty )
    {
        rsp_controller::current = this;

        //server message contains commands for frames from ackstateid to refstateid
        ushort refstateid;
        bin >> refstateid;
        
        //dref is difference between currently received ref state and the previous one
        short dref = refstateid - (ushort)_refstateid;
        if( dref <= 0 )
            return -1; //not patched, old msg

        int bsr = 0;
        if( ty == RSP_PACKET::FULL )
        {
            //send fast ack early for full updates
            bin << (uint8)RSP_PACKET::DIFF << ssid << slot << (ushort)(_refstateid+dref)
                << (uint8)0;
            bin.flush();


            _list.eat_local_commands(dref);

            _refstateid += dref;
            _stateid = _refstateid;
            //_list.process_ref_frame_final(0);

            _syncstatus = 1;
            bsr = 2;    //initial resync
        }
        else    //RSP_PACKET::DIFF
        {
            ushort ackstateid;
            bin >> ackstateid;

            //number of commands in the update
            short dcmd = refstateid - ackstateid;
            DASSERT( dcmd >= dref );

            //dack is difference between old ref state and acked server state
            // this equals number of trailing commands to skip as the client has them
            // already but the server doesn't know it
            short dack = (ushort)_refstateid - ackstateid;

            for( ; dref>0; --dref )
            {
                _stateid = _refstateid;
                bsr += _list.process_ref_frame();

                ++_refstateid;
            }

            DASSERT( refstateid == (ushort)_refstateid );

            //TODO: bound state should be reset when server sent additional frame data
            // but not when these are returned data for the owned object (and match)

            if( int(_bndstateid-_refstateid) < 0
                || bsr > 0
                || upd._changed
                )
                bsr = 1;    //resync
        }


//        rsp_c_update upd( this, bin, dack, dcmd );
//        _list.patch(upd);

        return bsr;
    }


    void resync()
    {
        _bndstateid = _refstateid;
        _list.sync_bound_objects();
    }


    ///Update bound objects to specified state id
    int interpolate( uint stateid, binstream& bin, uint32 ssid, uint16 slot )
    {
        if(!_syncstatus)
            return 0;

        //current time
        stateid += RSPC_SETTINGS._nleading;

        rsp_controller::current = this;
        _stateid = _bndstateid;

        int n = int(stateid-_bndstateid);

        if(n>0)
        {
            //LOG << "interpolating state " << stateid << " (+" << n << " states)\n";

            int dbr = _bndstateid - _refstateid;
            DASSERT( dbr >= 0 );

            for( int i=0; i<n; ++i )
            {
                _list.process_bound_frame(dbr++);
                
                ++_bndstateid;
                ++_stateid;
            }

            if( int(stateid - _lastcmdid) > 0 )     //send msg if this is a new frame
            {
                DASSERT( bin.get_size() == 0 );
                bin << (uint8)RSP_PACKET::DIFF << ssid << slot
                    << (ushort)_refstateid;

                rsp_c_object_state* cos = _list.get_owned_object();
                if(cos)
                {
                    //last frame (not yet processed) may still contain some commands
                    // nice to send them too
                    cos->prepare_frame( dbr + RSPC_SETTINGS._cmdlag );

                    uint max = stateid - RSPC_SETTINGS._nleading - _refstateid;
                    max += RSPC_SETTINGS._nleading + RSPC_SETTINGS._cmdlag + 1;

                    uint ncmd, pos;
                    if( (ncmd = cos->write_client_commands(bin,max,&pos)) != 0 ) {
                        LOG(2) {
                            *TLOG << timefmt(_refstateid)
                            << " sending " << ncmd << " commands from: " << timefmt(_refstateid+pos-RSPC_SETTINGS._nleading)
                            << " at " << timefmt()
                            << BINSTREAM_FLUSH;
                        }
                    }
                }
                else
                    bin << (uint8)0;
                bin.flush();

                _lastcmdid = stateid;
            }
        }

        return n;
    }

    ///Push command to queue and return and binstream
    binstream* xpush_cmd()
    {
        if( _syncstatus == 0 )
            return 0;

        uint stateid = _Timer.ticks();

        //_curcmd points to ref state, place the command nw+1 positions ahead
        // client commands is placed +2 slots forward from stateid
        // so thath after the next client transition it's at nw+1
        int i = RSPC_SETTINGS._cmdlag + stateid - _refstateid + RSPC_SETTINGS._nleading;

        if( i >= 4*(int)RSPC_SETTINGS._nleading )
            return 0;
    
        if( i<0 )
        {
            LOG(0) {
                *TLOG << "CMD REJECTED " << timefmt(_refstateid+i-RSPC_SETTINGS._nleading)
                << "(+" << i << ")  at " << timefmt() << BINSTREAM_FLUSH;
            }
            return 0;
        }

        rsp_c_object_state* os = _list.get_owned_object();
        if(!os)
            return 0;

        LOG(2) {
            *TLOG << "CMD " << timefmt(_refstateid+i-RSPC_SETTINGS._nleading)
            << "(+" << i << ")  at " << timefmt() << BINSTREAM_FLUSH;
        }

        return &os->push_cmd(i);
    }

    uint get_bound_state_id() const         { return _bndstateid; }
    uint get_ref_state_id() const           { return _refstateid; }

    void set_refstateid( uint rsid )
    {
        _stateid = _refstateid = _bndstateid = _lastcmdid = rsid;
        _syncstatus = 0;
    }



    //rsp_c_object_state* get_object( uint n ) const          { return _list.get_object(n); }
    //rsp_c_object_state* find_object( rsp_base* p ) const    { return _list.find_object(p); }
    //rsp_c_object_state* find_object( uint oid ) const       { return _list.find_object(oid); }

    //rsp_c_object_state* get_from_ptr( rsp_base* p );

    rsp_c_controller() : rsp_controller( rsp_controller::CT_CLIENT )
    {
        _syncstatus = 0;
        _refstateid = _bndstateid = _lastcmdid = 0;
    }

    void reset()
    {
        _syncstatus = 0;
        _bndstateid = _lastcmdid = _refstateid;
    }


protected:

    uint _refstateid;                               ///< reference state id (for sending acks)
    uint _bndstateid;                               ///< state id of bound objects (updated)
    uint _lastcmdid;
    //rsp_c_objlist _list;                            ///< primary object list

    int  _syncstatus;                               ///< 0 didn't receive first sync
};

#endif //__RSP_C_CONTROLLER__HEADER_FILE__
